#include "cg_local.h"
#include "../game/bg_lua.h"

enum
{
	LSYSCALL_CL_CREATEANCHOR			= 1001,	// (anchorType, ...)
	LSYSCALL_CL_CREATERENDERABLE		= 1002,	// (renderableType, anchor[, ...])
	LSYSCALL_CL_GETMODEL				= 1003,	// (modelName[, skelName])
	LSYSCALL_CL_GETANIMATIONCONTROLLER	= 1004,	// (acPath, skelPath)
	LSYSCALL_CL_SETRENDERABLEPROPERTY	= 1005,	// (renderableID, propertyID, value)
	LSYSCALL_CL_DELETEANCHOR			= 1006,	// (anchorID)

	LSYSCALL_CL_TUNERSETVALUE			= 1007,	// (renderableID, tunerIndex, value, tweenTime)
	LSYSCALL_CL_TUNERDISCHARGETRIGGER	= 1008,	// (renderableID, tunerIndex, tweenTime)
	LSYSCALL_CL_TUNERSTOPTWEENING		= 1009,	// (renderableID, tunerIndex)
	LSYSCALL_CL_TUNERSTOPTRIGGER		= 1010,	// (renderableID, tunerIndex, tweenTime)
};


enum
{
	LRPROPERTY_ANIMATIONCONTROLLER,
};

enum
{
	LFIELDTYPE_ORIGIN = LFIELDTYPE_SPECIAL,
	LFIELDTYPE_EFLAGS,
};


centity_t *CG_LuaValidateWorldObjectOID(lua_Number oid)
{
	int intOID;
	centity_t *result;

	intOID = (int)oid;
	if(intOID < 0 || intOID >= MAX_GENTITIES)
		Com_Error(ERR_DROP, "Bad object ID referenced");
	result = cg_entities + intOID;
	if(!result->currentValid)
		Com_Error(ERR_DROP, "Freed object was referenced");

	return result;
}


int CG_LuaWorldObjectPropertyValue(lua_State *L, int oid, const bg_luaSpecialProperty_t *sp)
{
	vec3_t v3;
	centity_t *ent = CG_LuaValidateWorldObjectOID(oid);

	switch(sp->readHandlingType)
	{
	case LFIELDTYPE_ORIGIN:
		BG_EvaluateTrajectory(&ent->currentState.pos, cg.time, v3);
		BG_PushVector3v(L, v3);
		return 3;
	default:
		return BG_LuaReadSpecialProperty(L, ent, sp, sp->eFieldOffset);
	};
}

int CG_LuaSetWorldObjectProperty(lua_State *L, int oid, const bg_luaSpecialProperty_t *sp, int valueIndex)
{
	centity_t *ent = CG_LuaValidateWorldObjectOID(oid);

	switch(sp->writeHandlingType)
	{
	default:
		return BG_LuaWriteSpecialProperty(L, valueIndex, ent, sp, sp->eFieldOffset);
	};
}


// Class definitions
static const bg_luaSpecialProperty_t cg_luaWorldObjectProperties[] =
{
	{ "origin", 0, 0, LFIELDTYPE_VECTOR3, LFIELDTYPE_NOACCESS, "vector3" },
	{ "efTeleport", TFOFS(centity_t, currentState.eFlags), TFOFS(playerState_t, eFlags), LFIELDTYPE_BITFIELD, LFIELDTYPE_BITFIELD, NULL, EF_TELEPORT_BIT },
};


static const bg_luaSystemClass_t cg_luaSystemClasses[] =
{
	{
		"WorldObject",
		cg_luaWorldObjectProperties,
		(sizeof(cg_luaWorldObjectProperties) / sizeof(cg_luaWorldObjectProperties[0])),
		CG_LuaWorldObjectPropertyValue,
		CG_LuaSetWorldObjectProperty,
	},
};

#define NUM_SYSTEM_CLASSES (sizeof(cg_luaSystemClasses) / sizeof(cg_luaSystemClasses[0]))

int CG_LuaCreateAnchor(lua_State *L)
{
	// Get the anchor for this
	leAnchor_t *anchor = CG_AllocAnchor();

	if(!anchor)
	{
		lua_pushnil(L);
		return 1;
	}

	anchor->anchorType = lua_tointeger(L, 2);

	switch(anchor->anchorType)
	{
	case LANCHOR_WORLDOBJECT:
		lua_getfield(L, 3, "_META");
		// _META
		lua_getfield(L, -1, "nativeObjectID");
		// _META / ID

		anchor->worldObjectID = lua_tointeger(L, -1);
		lua_pop(L, 2);

		if(anchor->worldObjectID < 0 || anchor->worldObjectID >= MAX_GENTITIES)
		{
			lua_pushstring(L, "Attempted to create anchor based on invalid worldobject ID");
			lua_error(L);
		}
		break;
	};

	// Return anchor number
	lua_pushinteger(L, anchor - cg_anchors);
	return 1;
}

int CG_LuaCreateRenderable(lua_State *L)
{
	localEntity_t *le;
	leAnchor_t *anchor;
	int anchorNum = lua_tointeger(L, 3);

	if(anchorNum < 0 || anchorNum >= MAX_ANCHORS || !cg_anchors[anchorNum].inUse)
	{
		// Invalid anchor
		lua_pushnil(L);
		return 1;
	}

	anchor = cg_anchors + anchorNum;

	// Invalid
	le = CG_AllocLocalEntity(qfalse);

	if(!le)
	{
		lua_pushnil(L);
		return 1;
	}

	// Attach the anchor
	le->leType = LE_SCRIPTRENDERABLE;
	le->srType = lua_tointeger(L, 2);
	le->anchor = anchor;
	le->endTime = -1;

	switch(le->srType)
	{
	case LRENDERABLE_MODEL:
		le->refEntity.hModel = lua_tointeger(L, 4);
		break;
	};

	lua_pushnumber(L, le - cg_localEntities);
	return 1;
}

localEntity_t *CG_LuaRenderableFromParameter(lua_State *L, int idx)
{
	localEntity_t *le;
	int leid = lua_tointeger(L, idx);

	if(leid < 0 || leid >= MAX_LOCAL_ENTITIES)
		return NULL;	// Bad ID

	le = cg_localEntities + leid;
	if(!le->prev || le->leType != LE_SCRIPTRENDERABLE)
		return NULL;	// Not active

	return le;
}

//(renderableID, propertyID, value)
int CG_LuaSetRenderableProperty(lua_State *L)
{
	localEntity_t *le = CG_LuaRenderableFromParameter(L, 2);

	if(!le) return 0;

	switch(lua_tointeger(L, 3))
	{
	case LRPROPERTY_ANIMATIONCONTROLLER:
		le->refEntity.hAnimationController = lua_tointeger(L, 4);
		memset(le->tuners, 0, sizeof(le->tuners));
		break;
	};

	return 0;
}


int CG_LuaTunerSetValue(lua_State *L)
{
	int tunerIndex, tweenTime;
	leTuner_t *tuner;
	localEntity_t *le = CG_LuaRenderableFromParameter(L, 2);
	float tunerValue;

	if(!le) return 0;

	tunerIndex = lua_tointeger(L, 3);
	if(tunerIndex < 0 || tunerIndex >= MAX_ANIMATION_TUNERS) return 0;

	tuner = le->tuners + tunerIndex;

	tunerValue = lua_tonumber(L, 4);
	tweenTime = lua_isnil(L, 5) ? 0 : (int)(lua_tonumber(L, 5) * 1000.0f);

	switch(tuner->tweenType)
	{
	case LE_TUNER_TWEEN_OFF:
		if(tweenTime)
		{
			// Tween to this
			tuner->tweenType = LE_TUNER_TWEEN_ON_NOPENDING;
			tuner->prev.timeStamp = cg.time;
			tuner->current.timeStamp = cg.time + tweenTime;
			tuner->current.value = tunerValue;
		}
		else
			tuner->prev.value = tunerValue;
		break;
	case LE_TUNER_TWEEN_ON_NOPENDING:
	case LE_TUNER_TWEEN_ON_PENDING:
		tuner->tweenType = LE_TUNER_TWEEN_ON_PENDING;
		tuner->pending.value = tunerValue;
		tuner->pending.timeStamp = tuner->current.timeStamp + tweenTime;
		break;
	};

	return 0;
}

int CG_LuaTunerDischargeTrigger(lua_State *L, int active)
{
	int tunerIndex, tweenTime;
	leTuner_t *tuner;
	localEntity_t *le = CG_LuaRenderableFromParameter(L, 2);

	if(!le) return 0;

	tunerIndex = lua_tointeger(L, 3);
	if(tunerIndex < 0 || tunerIndex >= MAX_ANIMATION_TUNERS) return 0;

	tuner = le->tuners + tunerIndex;

	tweenTime = lua_isnil(L, 4) ? 0 : (int)(lua_tonumber(L, 4) * 1000.0f);

	switch(tuner->tweenType)
	{
	case LE_TUNER_TWEEN_OFF:
		if(tweenTime)
		{
			// Tween to this
			tuner->tweenType = LE_TUNER_TWEEN_ON_NOPENDING;
			tuner->prev.timeStamp = cg.time;
			tuner->current.timeStamp = cg.time + tweenTime;
			tuner->current.value = active ? (float)(tuner->current.timeStamp) / 1000.0f : 0.0f;
		}
		else
			tuner->prev.value = active ? ((float)cg.time) / 1000.0f : 0.0f;
		break;
	case LE_TUNER_TWEEN_ON_NOPENDING:
	case LE_TUNER_TWEEN_ON_PENDING:
		tuner->tweenType = LE_TUNER_TWEEN_ON_PENDING;
		tuner->pending.timeStamp = tuner->current.timeStamp + tweenTime;
		tuner->pending.value = active ? (float)(tuner->pending.timeStamp) / 1000.0f : 0.0f;
		break;
	};

	return 0;
}

int CG_LuaTunerStopTweening(lua_State *L)
{
	int tunerIndex;
	leTuner_t *tuner;
	localEntity_t *le = CG_LuaRenderableFromParameter(L, 2);

	if(!le) return 0;

	tunerIndex = lua_tointeger(L, 3);
	if(tunerIndex < 0 || tunerIndex >= MAX_ANIMATION_TUNERS) return 0;

	tuner = le->tuners + tunerIndex;

	switch(tuner->tweenType)
	{
	case LE_TUNER_TWEEN_ON_NOPENDING:
		tuner->prev = tuner->current;
		break;
	case LE_TUNER_TWEEN_ON_PENDING:
		tuner->prev = tuner->pending;
		break;
	};
	tuner->tweenType = LE_TUNER_TWEEN_OFF;

	return 0;
}


int CG_LuaGetModel(lua_State *L)
{
	qhandle_t mdl;
	const char *meshName;
	const char *skelName;

	meshName = lua_tostring(L, 2);

	if(lua_isnil(L, 3))
		skelName = meshName;
	else
		skelName = lua_tostring(L, 3);

	mdl = trap.R_RegisterModel(meshName, skelName);

	if(mdl)
		lua_pushinteger(L, mdl);
	else
		lua_pushnil(L);

	return 1;
}

int CG_LuaGetAnimationController(lua_State *L)
{
	qhandle_t ac;
	const char *acName;
	const char *skelName;

	acName = lua_tostring(L, 2);

	if(lua_isnil(L, 3))
		skelName = acName;
	else
		skelName = lua_tostring(L, 3);

	ac = trap.R_RegisterAnimationController(acName, skelName);

	if(ac)
		lua_pushinteger(L, ac);
	else
		lua_pushnil(L);

	return 1;
}


int CG_LuaSysCall(lua_State *L)
{
	int callID;
	int oid;
	char ccmd[MAX_STRING_CHARS];

	if(lua_type(L, 1) != LUA_TNUMBER)
	{
		lua_pushstring(L, "SysCall with non-numeric call ID");
		lua_error(L);
	}

	callID = lua_tointeger(L, 1);

	switch(callID)
	{
	case LSYSCALL_LOCALE:
		lua_pushstring(L, "client");
		return 1;
	case LSYSCALL_SPECIALPROPERTYVALUE:
		return BG_LuaSpecialPropertyValue(L, cg_luaSystemClasses, NUM_SYSTEM_CLASSES);
	case LSYSCALL_SETSPECIALPROPERTY:
		return BG_LuaSetSpecialProperty(L, cg_luaSystemClasses, NUM_SYSTEM_CLASSES);
	case LSYSCALL_OBJECTCLIENTID:
		oid = lua_tointeger(L, 2);
		if(oid < 0 || oid >= cgs.maxclients)
			lua_pushnil(L);
		else
			lua_pushnumber(L, oid);
		return 1;
	case LSYSCALL_REPLICATEEVENT:
		BG_LuaBuildEventCommand(L, ccmd, sizeof(ccmd));
		trap.SendTimedClientCommand(ccmd);
		return 0;
	case LSYSCALL_CL_CREATEANCHOR:
		return CG_LuaCreateAnchor(L);
	case LSYSCALL_CL_CREATERENDERABLE:
		return CG_LuaCreateRenderable(L);
	case LSYSCALL_CL_GETMODEL:
		return CG_LuaGetModel(L);
	case LSYSCALL_CL_GETANIMATIONCONTROLLER:
		return CG_LuaGetAnimationController(L);
	case LSYSCALL_CL_SETRENDERABLEPROPERTY:
		return CG_LuaSetRenderableProperty(L);

	case LSYSCALL_CL_TUNERSETVALUE:
		return CG_LuaTunerSetValue(L);
	case LSYSCALL_CL_TUNERDISCHARGETRIGGER:
		return CG_LuaTunerDischargeTrigger(L, 1);
	case LSYSCALL_CL_TUNERSTOPTWEENING:
		return CG_LuaTunerStopTweening(L);
	case LSYSCALL_CL_TUNERSTOPTRIGGER:
		return CG_LuaTunerDischargeTrigger(L, 0);

	default:
		return BG_LuaSysCall(L, callID);
	};
}


lua_State *cg_luaState = NULL;

// LuaAlloc, copped from l_alloc in the reference manual
// (c) 2006-2007 Lua.org, PUC-Rio
void *CG_LuaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
	if (nsize == 0)
	{
		free(ptr);
		return NULL;
	}
	else
		return realloc(ptr, nsize);
}

int CG_LuaAtPanic(lua_State *L)
{
	const char *err = luaL_checkstring(L, 1);

	Com_Error( ERR_DROP, "Client Lua error: %s", err );

	return 1;
}

const char *CG_LuaReadFromStream(lua_State *L, void *data, size_t *size)
{
	fileHandle_t *fh;
	static char chunk[1000];
	int bytesRead;

	fh = (fileHandle_t *)data;

	bytesRead = trap.FS_Read(chunk, 1000, *fh);

	if(!bytesRead)
		return NULL;

	*size = bytesRead;

	return chunk;
}

void CG_LuaLoadScript( const char *filename )
{
	qboolean useInvoke;
	fileHandle_t fh;
	int errors;

	useInvoke = qtrue;
	if(filename[0] == '*')
	{
		useInvoke = qfalse;
		filename++;
	}

	if(trap.FS_FOpenFile(va("lua/%s", filename), &fh, FS_READ) == -1)
		Com_Error(ERR_DROP, "Could not open Lua script %s", filename);

	if(useInvoke)
		lua_getglobal(cg_luaState, "traceInvoke");

	errors = lua_load(cg_luaState, CG_LuaReadFromStream, &fh, filename);
	trap.FS_FCloseFile(fh);

	if(errors)
		Com_Error(ERR_DROP, "Script %s failed to load: %s", filename, luaL_checkstring(cg_luaState, -1));

	if(useInvoke)
		lua_call(cg_luaState, 1, 0);
	else
		lua_call(cg_luaState, 0, 0);
}

void CG_LuaLoadManifest( const char *manifestName )
{
	int numBytes;
	fileHandle_t fh;
	char manifestFile[10000];
	char *token, *token_p;

	Com_Printf("Loading manifest lua/%s.manifest...\n", manifestName);

	numBytes = trap.FS_FOpenFile( va("lua/%s.manifest", manifestName), &fh, FS_READ );
	if(numBytes == -1)
		Com_Error(ERR_DROP, "Could not open manifest %s", manifestName);
	if(!numBytes)
	{
		trap.FS_FCloseFile(fh);
		return;
	}

	if(numBytes >= sizeof(manifestFile) - 1)
		Com_Error(ERR_DROP, "Lua manifest %s is too long", manifestFile);

	trap.FS_Read(manifestFile, numBytes, fh);
	manifestFile[numBytes] = '\0';
	trap.FS_FCloseFile(fh);

	token_p = manifestFile;

	for(;;)
	{
		token = COM_Parse(&token_p);
		if(!token_p)
			break;
		Com_Printf("...%s\n", token);
		CG_LuaLoadScript(token);
	}
}

#define ADD_CONSTANT(n)	\
	lua_pushnumber(cg_luaState, n);	\
	lua_setfield(cg_luaState, -2, #n)

void CG_LuaInit( void )
{
	if(cg_luaState)
		lua_close(cg_luaState);

	cg_luaState = lua_newstate(CG_LuaAlloc, NULL);
	if(!cg_luaState)
		Com_Error( ERR_DROP, "Lua state could not be initialized" );

	lua_atpanic(cg_luaState, CG_LuaAtPanic);

	// Load standard libs
	BG_LuaLoadStandardLibraries(cg_luaState);

	// Add constants
	lua_getglobal(cg_luaState, "SystemConstants");
	ADD_CONSTANT(LRENDERABLE_MODEL);

	ADD_CONSTANT(LRPROPERTY_ANIMATIONCONTROLLER);

	ADD_CONSTANT(LANCHOR_ORIGIN);
	ADD_CONSTANT(LANCHOR_WORLDOBJECT);
	ADD_CONSTANT(LANCHOR_CAMERA);

	lua_pop(cg_luaState, 1);

	lua_pushcfunction(cg_luaState, CG_LuaSysCall);
	lua_setglobal(cg_luaState, "syscall");

	// Set special properties
	BG_LuaRegisterClasses(cg_luaState, cg_luaSystemClasses, NUM_SYSTEM_CLASSES);

	// Load all manifests
	CG_LuaLoadManifest("common");
	CG_LuaLoadManifest("tertiumbellum");
}

void CG_LuaReceiveEvent(const char *evt)
{
	lua_getglobal(cg_luaState, "executeClientEvent");
	lua_pushstring(cg_luaState, evt);
	lua_call(cg_luaState, 1, 0);
}

void CG_LuaSignalLeaveScope(int entityNum)
{
	lua_getglobal(cg_luaState, "traceInvokeV");
	// TI
	lua_getglobal(cg_luaState, "notifyEntityDestroy");
	// TI / NED
	lua_pushinteger(cg_luaState, entityNum);
	// TI / NED / PI
	lua_call(cg_luaState, 2, 0);
}

void CG_LuaSignalEnterScope(int entityNum, int classID)
{
	// Spawn in gamecode
	lua_getglobal(cg_luaState, "traceInvokeV");
	// TI
	lua_getglobal(cg_luaState, "become");
	// TI / B

	lua_createtable(cg_luaState, 0, 1);
	// TI / B / I
	lua_createtable(cg_luaState, 0, 1);
	// TI / B / I / META
	lua_pushnumber(cg_luaState, entityNum);
	// TI / B / I / META / id
	lua_setfield(cg_luaState, -2, "nativeObjectID");
	// TI / B / I / META
	lua_setfield(cg_luaState, -2, "_META");
	// TI / B / I

	lua_pushinteger(cg_luaState, classID);
	// TI / B / I / classID
	lua_call(cg_luaState, 3, 0);
}

void CG_LuaShutdown( void )
{
	if(cg_luaState)
		lua_close(cg_luaState);
	cg_luaState = NULL;
}

void CG_LuaCommand_f( void )
{
	int i, n;

	n = trap.Argc();
	if ( n < 1 )
		return;

	// Push invoke
	lua_getglobal(cg_luaState, "traceInvokeV");
	// Push console command exec func
	lua_getglobal(cg_luaState, "runConsoleCommand");
	// Push the command
	lua_pushstring(cg_luaState, CG_Argv(1));

	// Push parameters table
	lua_createtable(cg_luaState, n - 1, 0);

	for(i=2;i<n;i++)
	{
		lua_pushinteger(cg_luaState, i-1);
		lua_pushstring(cg_luaState, CG_Argv(i));
		lua_rawset(cg_luaState, -3);
	}

	// Call it
	lua_call(cg_luaState, 3, 0);
}
